package com.juma.cartridge.retryque.aop;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import com.juma.cartridge.common.HessianIOUtils;
import com.juma.cartridge.retryque.DefaultRetryQueueManager;
import com.juma.cartridge.retryque.RetryHandler;
import com.juma.cartridge.retryque.RetryQueueManager;
import com.juma.cartridge.retryque.RetryTask;

@Aspect
public class RetryQueueAspect implements BeanFactoryPostProcessor,BeanPostProcessor {

	private DefaultRetryQueueManager retryQueueManager;
	
	private Map<String,RetryHandler> cacheRetryHandlers = new HashMap<String,RetryHandler>();
	
	private String basePackages;
	
	
	
	@Pointcut("@annotation(com.juma.cartridge.retryque.aop.Retry)")
	public void retryPoint() {}
	
	@Around("retryPoint()")
	public void intercept(ProceedingJoinPoint point) throws Throwable {
		
		Retry annot  = getRetryAnnotation(point);
		String bizCode = annot.bizCode();		
		Object[] args = point.getArgs();
		try {
			point.proceed(args);
		}catch(Throwable t) {
			if(RetryTaskHolder.O.get()==null && exceptionIn(t,annot.when()) || 
					(annot.ignores().length>0 && !exceptionIn(t,annot.ignores()))) {
			    byte[] bytes = HessianIOUtils.serialize(args);
				RetryTask task = new RetryTask();
				task.setBizCode(bizCode);
				task.setBizBin(bytes);
				task.setDetail(t.getMessage());
				retryQueueManager.enqTask(task);	
			} else {
				throw t;
			}
		}
	}

	private boolean exceptionIn(Throwable t,Class<? extends Throwable>[] clazzes) {
		if(t!=null && clazzes!=null) {
			for(Class<? extends Throwable> clazz:clazzes) {
				if(clazz.isAssignableFrom(t.getClass())) {
					return true;
				}
			}
		}
		return false;
	}
	
	private Retry getRetryAnnotation(ProceedingJoinPoint pjp) {
		Signature sig = pjp.getSignature();
		MethodSignature msig = null;
		if (!(sig instanceof MethodSignature)) {
			return null;
		}
		msig = (MethodSignature) sig;
		Object target = pjp.getTarget();
		try {
			Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
			return currentMethod.getAnnotation(Retry.class);
		}catch(Exception e){
			return null;
		}
		
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		if(bean instanceof RetryQueueManager) {
			retryQueueManager = (DefaultRetryQueueManager)bean;
			retryQueueManager.setRetryHandlers(cacheRetryHandlers);
			cacheRetryHandlers.clear();
			cacheRetryHandlers = null;
		}
		return bean;
	}
	
	
	@Override
	public void postProcessBeanFactory(
			ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
		
		Set<String> packages = new TreeSet<String>();
		
		if(basePackages!=null) {
			String[] pks = basePackages.split(",");
			for (String pk:pks) {
				if(pk.trim().isEmpty()) {
					continue;
				}
				packages.add(pk);
			}
		}
		
		String[] names = beanFactory.getBeanDefinitionNames();
		for (String name:names) {
			String className = beanFactory.getBeanDefinition(name).getBeanClassName();
			if(!matchPackage(packages,className)) {
				continue;
			}
			Map<String,RetryHandler> handlers = parseRetryHandler(className,beanFactory);
			if (handlers!=null && !handlers.isEmpty()) {
				this.cacheRetryHandlers.putAll(handlers);
			}
		}
	}

	
	private boolean matchPackage(Set<String> packages,String className) {
		if(packages==null||packages.isEmpty()) {
			return true;
		}
		
		for (String pkg:packages) {
			if(className.startsWith(pkg)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 从class中解析@Retry注解并构建成RetryHandler实例
	 * @param bean
	 * @return
	 */
	private Map<String,RetryHandler> parseRetryHandler(String className,BeanFactory beanFactory) {
		
		Class<?> clazz = null;
		try {
			clazz = Class.forName(className);	
		}catch(Exception e){
			return null;
		}
		
		Map<String,RetryHandler> handlers = new HashMap<String,RetryHandler>();
		Method[] methods = clazz.getMethods();
		for (Method method:methods) {
			Retry retry = method.getAnnotation(Retry.class);
			if(retry==null) {
				continue;
			}
			String bizCode = retry.bizCode();
			AspectRetryHandler handler = new AspectRetryHandler(beanFactory,method,retry.max());
			handler.setIntervalSeconds(retry.interval());
			handlers.put(bizCode, handler);
		}
		return handlers;
	}
	
	public void setBasePackages(String basePackages) {
		this.basePackages = basePackages;
	}
	
	
}
